﻿using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Devil;

namespace GameToolkit.Editor
{
    [CustomPropertyDrawer(typeof(MaskAttribute))]
    public class MaskDrawer : PropertyDrawer
    {
        bool mIsIndex;
        bool mIsEnum;
        int mColumes;
        System.Type mType;
        GUIContent[] enumNames;
        int[] enumValues;

        bool mSupport;
        float mHeight;
        readonly string[] togStyles = new string[] { "button", "buttonleft", "buttonmid", "buttonright" };

        public class EditValue
        {
            public SerializedProperty property;
            public int value;
            public bool isOn;
            public bool isEnum;
            public EditValue(SerializedProperty prop, int value, bool isOn, bool isEnum)
            {
                this.property = prop;
                this.value = value;
                this.isOn = isOn;
                this.isEnum = isEnum;
            }
        }

        int AsInteger(System.Type enumType, object value)
        {
            var untype = System.Enum.GetUnderlyingType(enumType);
            if (untype == typeof(byte))
                return (byte)value;
            else if (untype == typeof(int))
                return (int)value;
            else if (untype == typeof(uint))
                return (int)(uint)value;
            else if (untype == typeof(short))
                return (short)value;
            else if (untype == typeof(ushort))
                return (ushort)value;
            return 0;
        }

        void GetNames(MaskAttribute att)
        {
            if(mType != att.EnumType || mIsIndex != att.IsIndexEnum || mColumes != att.ToggleColumns || att.AsEnums != mIsEnum)
            {
                mIsEnum = att.AsEnums;
                mColumes = att.ToggleColumns;
                mIsIndex = att.IsIndexEnum;
                mType = att.EnumType;
                var ns = System.Enum.GetNames(mType);
                List<string> names = new List<string>(ns.Length);
                List<int> values = new List<int>(ns.Length);
                var vs = System.Enum.GetValues(mType);
                if (mIsIndex && !mIsEnum)
                {
                    for (int i = 0; i < vs.Length; i++)
                    {
                        var id = AsInteger(mType, vs.GetValue(i));
                        var v = 1 << id;
                        if ((att.ValidMask & v) != 0)
                        {
                            values.Add(v);
                            names.Add(ns[i]);
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < vs.Length; i++)
                    {
                        var v = AsInteger(mType, vs.GetValue(i));
                        if ((att.ValidMask & v) != 0)
                        {
                            values.Add(v);
                            names.Add(ns[i]);
                        }
                    }
                }
                if (!mIsEnum)
                {
                    for (int i = values.Count - 1; i >= 0; i--)
                    {
                        if (values[i] == 0)
                        {
                            values.RemoveAt(i);
                            names.RemoveAt(i);
                        }
                    }
                }
                if(mColumes < 1 && !mIsEnum)
                {
                    names.Insert(0, "[ All ]");
                    values.Insert(0, att.ValidMask);
                    names.Insert(0, "[ Nothing ]");
                    values.Insert(0, 0);
                }
                enumNames = new GUIContent[names.Count];
                for (int i = 0; i < names.Count; i++)
                {
                    enumNames[i] = new GUIContent(FormatName(names[i]));
                }
                enumValues = values.ToArray();
            }
        }

        string FormatName(string fname)
        {
            fname = Regex.Replace(fname, "([a-z0-9][A-Z])|(_[a-zA-Z])", (match) =>
            {
                var c = match.Value[0];
                var c1 = match.Value[1];
                if (c == '_')
                {
                    if (c1 >= 'a' && c1 <= 'z')
                        c1 = (char)(c1 + 'A' - 'a');
                    return ParallelUtils.Concat(' ', c1);
                }
                else
                {
                    return ParallelUtils.Concat(c, ' ', c1);
                }
            });
            return fname;
        }

        string NameMask(int value)
        {
            if(mIsEnum)
            {
                for (int i = 2; i < enumValues.Length; i++)
                {
                    if (enumValues[i] == value)
                    {
                        return enumNames[i].text;
                    }
                }
                return "[ Unknown ]";
            }
            if (value == 0)
                return "[ Nothing ]";
            if (value == -1)
                return "[ All ]";
            var buf = ParallelUtils.GetBuilder();
            int num = 0;
            for (int i = 2; i < enumValues.Length; i++)
            {
                if (enumValues[i] != 0 && (enumValues[i] & value) == enumValues[i])
                {
                    if (num > 0)
                        buf.Append(", ");
                    if (num >= 3)
                    {
                        buf.Append("...");
                        break;
                    }
                    buf.Append(enumNames[i].text);
                    num++;
                }
            }
            if (num == 0 && value != 0)
                buf.Append("[Mixed]...");
            return ParallelUtils.ReturnRelease(buf);
        }

        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            var att = attribute as MaskAttribute;
            mSupport = !(property.propertyType != SerializedPropertyType.Integer && property.propertyType != SerializedPropertyType.Enum
                || att.EnumType == null || !att.EnumType.IsEnum);
            
            mHeight = base.GetPropertyHeight(property, label);
            if (!mSupport)
                return mHeight * 2f;
            GetNames(att);
            if(mColumes > 0)
            {
                var rows = Mathf.CeilToInt(enumValues.Length / (float)mColumes);
                return rows * mHeight;
            }
            return mHeight;
        }

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            EditorGUI.BeginProperty(position, label, property);
            if (!mSupport)
            {
                EditorGUI.HelpBox(position, string.Format("{0}:    this property don't support mask", label.text), MessageType.Warning);
            }
            else
            {
                //var att = attribute as PrefixAttribute;
                //if (att != null && !string.IsNullOrEmpty(att.Prefix))
                //    label.text = att.Prefix;
                int lv = EditorGUI.indentLevel;
                position = EditorGUI.PrefixLabel(position, label);
                EditorGUI.indentLevel = 0;
                if (mColumes > 0)
                {
                    var w = position.width / (float)mColumes;
                    var row = new Rect(position.x, position.y, w, mHeight);
                    for (int i = 0; i < enumValues.Length; i++)
                    {
                        var c = i % mColumes;
                        var isOn = (enumValues[i] & property.intValue) == enumValues[i];

                        var on = GUI.Toggle(new Rect(row.x + c * w, row.y, w, row.height), isOn, enumNames[i],
                            togStyles[mColumes == 1 ? 0 : (c == 0 ? 1 : (c == mColumes - 1 ? 3 : 2))]);
                        if (isOn != on)
                        {
                            if (mIsEnum && on)
                            {
                                property.intValue = enumValues[i];
                                GUI.changed = true;
                            }
                            else if (!mIsEnum)
                            {
                                if (on)
                                    property.intValue |= enumValues[i];
                                else
                                    property.intValue &= ~enumValues[i];
                                GUI.changed = true;
                            }
                        }
                        if (c == mColumes - 1)
                            row.y += mHeight;
                    }
                }
                else
                {
                    label.text = NameMask(property.intValue);
                    var rect = new Rect(position.x, position.y, position.width - 80, position.height);
                    if (GUI.Button(rect, label, "MiniPopup"))
                    {
                        GenericMenu.MenuFunction2 func = (data) =>
                        {
                            var dt = (EditValue)data;
                            if (dt.isEnum)
                            {
                                dt.property.intValue = dt.value;
                                dt.property.serializedObject.ApplyModifiedProperties();
                            }
                            else if (dt.value == 0 || dt.value == -1)
                            {
                                if (dt.property.intValue != dt.value)
                                {
                                    dt.property.intValue = dt.value;
                                    dt.property.serializedObject.ApplyModifiedProperties();
                                }
                            }
                            else if (dt.isOn)
                            {
                                dt.property.intValue &= ~dt.value;
                                dt.property.serializedObject.ApplyModifiedProperties();
                            }
                            else
                            {
                                dt.property.intValue |= dt.value;
                                dt.property.serializedObject.ApplyModifiedProperties();
                            }
                        };
                        var v = property.intValue;
                        var menu = new GenericMenu();
                        for (int i = 0; i < enumNames.Length; i++)
                        {
                            var isOn = mIsEnum ? (enumValues[i] == v) : (enumValues[i] != 0 && (v & enumValues[i]) == enumValues[i]);
                            menu.AddItem(enumNames[i], isOn, func, new EditValue(property, enumValues[i], isOn, mIsEnum));
                            if (i == 1)
                                menu.AddSeparator("");
                        }
                        menu.ShowAsContext();
                    }
                    DevilEditorUtility.Hint(new Rect(position.xMax - 80, position.y, 80, position.height), property.intValue.ToString());
                }
                EditorGUI.indentLevel = lv;
            }
            EditorGUI.EndProperty();
        }

    }
}